package ar.edu.itba.it.cg.grupo01.impl.shape.shader;

import java.awt.Color;
import java.io.File;
import java.io.IOException;
import java.util.List;

import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;

import ar.edu.itba.it.cg.grupo01.api.Intersection;
import ar.edu.itba.it.cg.grupo01.api.Ray;
import ar.edu.itba.it.cg.grupo01.api.scene.Scene;
import ar.edu.itba.it.cg.grupo01.api.shape.Shape;
import ar.edu.itba.it.cg.grupo01.api.shape.light.Light;
import ar.edu.itba.it.cg.grupo01.api.texture.Texture;
import ar.edu.itba.it.cg.grupo01.api.texture.TextureFilter;
import ar.edu.itba.it.cg.grupo01.impl.RayImpl;
import ar.edu.itba.it.cg.grupo01.impl.RayTraceInformation;
import ar.edu.itba.it.cg.grupo01.impl.RayTracer;
import ar.edu.itba.it.cg.grupo01.impl.RayTracer.ColorHolder;
import ar.edu.itba.it.cg.grupo01.impl.color.Colors;
import ar.edu.itba.it.cg.grupo01.impl.scene.optimization.Octree;
import ar.edu.itba.it.cg.grupo01.impl.shape.Plane;
import ar.edu.itba.it.cg.grupo01.impl.shape.Triangle;
import ar.edu.itba.it.cg.grupo01.impl.texture.BilinearFilter;
import ar.edu.itba.it.cg.grupo01.impl.texture.TextureImpl;

public class PhongShader extends Shader {

	private int samples;
	private float exp;
	private Color specColor;
	private Color diffColor;
	private Texture texture;

	public Texture getTexture() {
		return texture;
	}

	public void setTexturePath(String texturePath) {
		try {
			TextureFilter filter = new BilinearFilter();
			texture = new TextureImpl(new File(texturePath), filter);
		} catch (IOException e) {
			throw new RuntimeException("Unable to load texture: " + texturePath);
		}
	}

	public int getSamples() {
		return samples;
	}

	public float getExp() {
		return exp;
	}

	public Color getSpecColor() {
		return specColor;
	}

	public Color getDiffColor() {
		return diffColor;
	}

	@Override
	public float getReflectionK() {
		return 0.08f;
	}

	public void setDiffColor(Color color) {
		this.diffColor = color;
	}

	public void setSpecColor(Color color) {
		this.specColor = color;
	}

	public void setExp(float exp) {
		this.exp = exp;
	}

	public void setSamples(int samples) {
		this.samples = samples;
	}

	@Override
	public Color getShadeColor(Shape shape, Point3f intersectionPoint) {
		return getDiffColor(shape, intersectionPoint);
	}

	@Override
	public Color shadeLight(Light light, Shape shape,
			Point3f intersectionPoint, Ray ray, float contribution,
			float attenuation) {

		Color diffusiveColor = Colors.scale(getDiffColor(shape,
				intersectionPoint), contribution);

		Vector3f reflection = new Vector3f();
		Vector3f modifiedNormal = shape.getNormal(intersectionPoint);
		modifiedNormal.scale(2 * contribution);
		reflection.sub(light.getNormal(intersectionPoint), modifiedNormal);

		contribution = reflection.dot(ray.getDirection());

		Color specularColor = Color.BLACK;
		if (contribution > 0) {
			specularColor = Colors.scaleAdd(specularColor, (float) Math.pow(
					contribution, exp), light.getColor());
			specularColor = Colors.mulTo(specularColor, specColor);
		}

		return Colors.addTo(diffusiveColor, specularColor);

	}

	@Override
	public Color shadeReflection(Color reflectionColor) {
		return (samples > 0) ? Colors.mulTo(reflectionColor, specColor)
				: Color.BLACK;
	}

	private Color getDiffColor(Shape shape, Point3f intersectionPoint) {

		if (texture == null) {
			return diffColor;
		}

		return texture.getColorAtPoint(shape
				.getUVCoordinates(intersectionPoint));
	}

	@Override
	public void shade(RayTraceInformation info) {
		RayTracer tracer = info.getTracer();
		Point3f intersectionPoint = info.getIntersectionPoint();
		Shape shape = info.getShape();
		Scene scene = info.getScene();
		ColorHolder color = info.getColor();
		Vector3f normal = info.getNormal();
		Ray ray = info.getRay();
		if (info.getIntersection().getType().equals(Intersection.Type.IN)) {
			normal.negate();
		}

		for (Light light : scene.getLights()) {

			List<Vector3f> dirs = light.getLigthDirs(intersectionPoint);
			float attenuation = light.getAttenuation(intersectionPoint);
			int hits = 0;

			Octree octree = info.getOctree();
			List<Plane> planes = scene.getPlanes();
			for (Vector3f lightDir : dirs) {

				if (tracer.hitsLight(shape, light, new RayImpl(
						intersectionPoint, lightDir), octree, planes)) {
					hits++;
				}
			}

			if (hits > 0) {
				Vector3f lightDir = dirs.get(0);
				float dot = lightDir.dot(normal);

				if (shape instanceof Triangle && dot < 0) {
					dot = -dot;
				}

				if (dot > 0) {
					ColorHolder attenuatedLightColor = new ColorHolder(Colors
							.scale(light.getColor(), attenuation));
					attenuatedLightColor.mul(((float) hits) / dirs.size());
					Color diffuseColor = Colors
							.mulTo(getDiffColor(shape, intersectionPoint),
									attenuatedLightColor.getColor());
					color.madd(dot, diffuseColor);
				}

				Vector3f reflection = new Vector3f();
				Vector3f modifiedNormal = new Vector3f(normal);
				modifiedNormal.scale(2.0f * dot);
				reflection.sub(lightDir, modifiedNormal);

				dot = reflection.dot(ray.getDirection());

				if (dot > 0) {
					ColorHolder specularColor = new ColorHolder(Color.BLACK);
					specularColor.madd((float) Math.pow(dot, exp), light.getColor());
					specularColor.mul(specColor);
					color.add(specularColor);
				}

				if (samples > 0) {
					info.getReflectionColor().mul(specColor);
				} else {
					info.getReflectionColor().set(Color.BLACK);
				}
			}
		}

	}

}
